#include "viewer.h"
#include "doodlepad.h"
#include "onscreenmenu.h"

bool DoodlePad::firstTime() { return m_firstTime; }
bool DoodlePad::active() { return m_active; }
void DoodlePad::resetFirstTime() { m_firstTime = true; }

int DoodlePad::tag()
{ return OnScreenMenu::maskMenu()->tag(); }

int DoodlePad::thickness()
{ return OnScreenMenu::maskMenu()->thickness(); }

bool DoodlePad::paintArea()
{ return OnScreenMenu::paintMaskMenu()->paintArea(); }

bool DoodlePad::doodleMenuActive()
{
  return OnScreenMenu::paintMaskMenu()->menuActive() ||
         OnScreenMenu::maskMenu()->menuActive();
}


float* DoodlePad::depthMap() { return m_depthMap; }
QImage DoodlePad::doodleImage()
{
  generateImage();
  return m_doodleImage;
}

QList<QPolygon> DoodlePad::polylines() { return m_polylines; }

DoodlePad::DoodlePad(Viewer *viewer)
{  
  m_Viewer = viewer;
  m_active = false;
  m_firstTime = false;
  m_image = 0;
  m_depthMap = 0;
  m_doodleImage = QImage();
  m_gradImage = QImage();
  m_polylines.clear();
  m_selectImage = QImage();
  m_width = m_height = 0;

  m_prevMouseX = 0;
  m_prevMouseY = 0;
  
  m_depthScale = 1.0;
  m_depthShift = 0.0;
}

DoodlePad::~DoodlePad()
{
  m_active = false;
  m_firstTime = false;

  m_doodleImage = QImage();
  m_gradImage = QImage();

  if (m_depthMap) delete [] m_depthMap;
  m_depthMap = 0;

  if (m_image) delete [] m_image;
  m_image = 0;

  m_polylines.clear();
  m_selectImage = QImage();
  m_width = m_height = 0;
}

void
DoodlePad::activate()
{
  m_firstTime = true;
  m_active = true;
  m_polylines.clear();
  m_selectImage = QImage();

  OnScreenMenu::paintMaskMenu()->reset();
  OnScreenMenu::maskMenu()->reset();
}


void
DoodlePad::deactivate()
{
  m_active = false;
  m_firstTime = false;  
  m_width = m_height = 0;

  m_doodleImage = QImage();
  m_gradImage = QImage();

  if (m_depthMap) delete [] m_depthMap;
  m_depthMap = 0;

  if (m_image) delete [] m_image;
  m_image = 0;

  OnScreenMenu::paintMaskMenu()->reset();
  OnScreenMenu::maskMenu()->reset();
}

void
DoodlePad::newPolyline()
{
  if (m_doodleImage.isNull())
    return;

  m_polylines.append(QPolygon());
}

void
DoodlePad::addPoint(QPoint pt)
{
  if (m_doodleImage.isNull())
    return;

  int pidx = m_polylines.count()-1;
  if (pidx < 0)
    return;

  m_polylines[pidx] << QPoint(pt.x(),
			      m_height-1-pt.y());

  generateImage();
}

void
DoodlePad::generateGradImage()
{
  m_depthScale = 1.0;
  m_depthShift = 0.0;

  m_gradImage.fill(0);
  
  if (!m_depthMap)
    QMessageBox::information(0, "", "depthMap is 0 !!!");

  float gmin, gmax;
  gmin = 1.0;
  gmax = -1.0;
  for(int i=0; i<m_width*m_height; i++)
    {
      if (m_depthMap[i] < 1.0)
	{
	  gmin = qMin(gmin, m_depthMap[i]);
	  gmax = qMax(gmax, m_depthMap[i]);
	}
    }
  if (gmax > gmin)
    {
      m_depthScale = 1.0/(gmax-gmin);
      m_depthShift = -gmin;
    }
  else
    return;
 
  float *tmp = new float[m_width*m_height]; 
  memset(tmp, 0, 4*m_height*m_width);

  for(int i=1; i<m_width-1; i++)
    for(int j=1; j<m_height-1; j++)
      {	
	int ji = j*m_width + i;
	
	float d = m_depthMap[ji];
	if (d < 1.0)
	  {
	    int idx[8];
	    idx[0] = (j-1)*m_width+(i-1);
	    idx[1] = (j-1)*m_width+(i);
	    idx[2] = (j-1)*m_width+(i+1);
	    idx[3] = (j+1)*m_width+(i-1);
	    idx[4] = (j+1)*m_width+(i);
	    idx[5] = (j+1)*m_width+(i+1);
	    idx[6] = (j)*m_width+(i-1);
	    idx[7] = (j)*m_width+(i+1);
	    
	    bool ignore = false;
	    float tgmax = 0;
	    for (int gi=0; gi<8; gi++)
	      {
		float dg = m_depthMap[idx[gi]];
		if (dg < 1.0)
		  tgmax = qMax(tgmax, (float)fabs(dg-d));
		else
		  {
		    ignore = true;
		    break;
		  }
	      }
	    if (!ignore)
	      tmp[ji] = tgmax;
	  }
//	int ji0 = j*m_width + (i-1);
//	int ji1 = j*m_width + (i+1);
//	int ji2 = (j-1)*m_width + i;
//	int ji3 = (j+1)*m_width + i;
//
//	float d = m_depthMap[ji];
//	float d0 = m_depthMap[ji0];
//	float d1 = m_depthMap[ji1];
//	float d2 = m_depthMap[ji2];
//	float d3 = m_depthMap[ji3];
//
//	if (d < 1.0 && d0 < 1.0 && d1 < 1.0 && d2 < 1.0 && d3 < 1.0)
//	  {
//	    // find max difference between
//	    // current pixel and 8 neighbouring pixels
//	    float g = fabs(d - d0);
//	    g = qMax(g, fabs(d-d1));
//	    g = qMax(g, fabs(d-d2));
//	    g = qMax(g, fabs(d-d3));
////	    float dx = fabs(d0-d1);
////	    float dy = fabs(d2-d3);
////	    float g = qMax(dx, dy);
//	    
//	    tmp[ji] = g;
//	  }
      }

  gmin = gmax = tmp[0];
  for(int i=0; i<m_width*m_height; i++)
    {
      gmin = qMin(gmin, tmp[i]);
      gmax = qMax(gmax, tmp[i]);
    }

  if (gmax-gmin > 0)
    {
      for(int i=0; i<m_width; i++)
	for(int j=0; j<m_height; j++)
	  {
	    int ji = j*m_width + i;
	    if (m_depthMap[ji] < 1.0)
	      {
		float g = (tmp[ji] - gmin)/(gmax-gmin);
		g = (g-0.02)/0.1;
		g = qBound(0.0f, g, 1.0f);
		m_gradImage.setPixel(i, j, qRgba(200*g, 0, 0, 200*g));
	      }
	  }
    }

  delete [] tmp;
}

void
DoodlePad::generateImage()
{
  m_doodleImage.fill(qRgba(0, 0, 100, 100));
  
  int ji = 0;
  for(int j=0; j<m_height; j++)
    for(int i=0; i<m_width; i++)
      {
	if (m_depthMap[ji] < 1.0)
	  m_doodleImage.setPixel(i, j, 0);

	ji++;
      }

  if (m_polylines.count() == 0 &&
      m_selectImage.isNull())
    return;

  QPainter polyPainter(&m_doodleImage);
  polyPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);

  if (m_polylines.count()>0)
    {
      QBrush brush(QColor(50, 150, 50, 150));
      QPen pen(QColor(50, 150, 50, 150));
      pen.setCapStyle(Qt::RoundCap);
      pen.setJoinStyle(Qt::RoundJoin);
      polyPainter.setPen(pen);
//      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{
	  polyPainter.setBrush(brush);
	  for(int i=0; i<m_polylines.count(); i++)
	    polyPainter.drawPolygon(m_polylines[i]);
	}
//      else
//	{
//	  pen.setWidth(OnScreenMenu::paintMaskMenu()->dradius());
//	  //pen.setWidth(7);
//	  polyPainter.setPen(pen);
//	  for(int i=0; i<m_polylines.count(); i++)
//	    polyPainter.drawPolyline(m_polylines[i]);
//	}
    }

  if (! m_selectImage.isNull())
  polyPainter.drawImage(m_selectImageRect,
			m_selectImage);

}

void
DoodlePad::clearDoodleImage()
{
  if (m_doodleImage.isNull())
    return;

  m_polylines.clear();
  m_selectImage = QImage();

  generateImage();

  OnScreenMenu::paintMaskMenu()->reset();
  OnScreenMenu::maskMenu()->reset();
}

void
DoodlePad::drawDoodle(QSize isize)
{
  if (! m_active)
    return;
    
  if (m_firstTime)
    {
      glFinish();

      m_firstTime = false;

      m_width = isize.width();
      m_height = isize.height();

      if (m_depthMap) delete [] m_depthMap;
      m_depthMap = new float[m_width*m_height];

      if (m_image) delete [] m_image;
      m_image = new uchar[4*m_width*m_height];

      m_doodleImage = QImage(m_width, m_height, QImage::Format_ARGB32);
      m_gradImage = QImage(m_width, m_height, QImage::Format_ARGB32);
      
      glReadBuffer(GL_BACK);

      glReadPixels(0,
		   0,
		   m_width,
		   m_height,
		   GL_RGBA,
		   GL_UNSIGNED_BYTE,
		   m_image);

      glReadPixels(0,
		   0,
		   m_width,
		   m_height,
		   GL_DEPTH_COMPONENT,
		   GL_FLOAT,
		   m_depthMap);


      clearDoodleImage();

      generateGradImage();
    }


  // splat the saved image
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, m_width, 0, m_height, -1, 1);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glRasterPos2i(0,0);
  glDrawPixels(m_width,
	       m_height,
	       GL_RGBA,
	       GL_UNSIGNED_BYTE,
	       m_image);

  const uchar *gimg = m_gradImage.bits();
  glRasterPos2i(0,0);
  glDrawPixels(m_width,
	       m_height,
	       GL_RGBA,
	       GL_UNSIGNED_BYTE,
	       gimg);


  const uchar *img = m_doodleImage.bits();
  glRasterPos2i(0,0);
  glDrawPixels(m_width,
	       m_height,
	       GL_RGBA,
	       GL_UNSIGNED_BYTE,
	       img);

  glFlush();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  
  
  OnScreenMenu::maskMenu()->drawMenu();
  OnScreenMenu::paintMaskMenu()->drawMenu();
}


bool
DoodlePad::processKey(QKeyEvent *event)
{
  if (OnScreenMenu::paintMaskMenu()->menuActive() == false &&
      OnScreenMenu::maskMenu()->menuActive() == false)
    return false;

  if (OnScreenMenu::paintMaskMenu()->processKey(event))
    return true;

  return OnScreenMenu::maskMenu()->processKey(event);

}

bool
DoodlePad::mousePressEvent(QMouseEvent *event)
{
  int x = event->pos().x();
  int y = event->pos().y();
      
  if (event->button() == Qt::LeftButton)
    {
      m_prevMouseX = x;
      m_prevMouseY = y;
      
      if (OnScreenMenu::paintMaskMenu()->checkPaintArea(x, y))
	{
	  OnScreenMenu::paintMaskMenu()->togglePaintArea();
	  return true;
	}
      
      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{
	  QRect subrect;
	  QImage subimage = getImage(x, y, subrect);

	  if (! subimage.isNull())
	    {	      
	      addImage(subrect, subimage);

	      generateImage();

	      return true;
	    }
	}
      else
	{
	  newPolyline();
	  addPoint(event->pos());
	}
      return true;
    }
  else if (event->button() == Qt::RightButton)
    {
      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{
	  QRect subrect;
	  QImage subimage = getImage(x, y, subrect);
	  
	  if (! subimage.isNull())
	    {	      
	      if (removeImage(subrect, subimage))
		{
		  generateImage();		  
		  return true;
		}
	    }
	}
      //clearDoodleImage();
      //return true;
    }

  return false;
}


bool
DoodlePad::mouseMoveEvent(QMouseEvent *event)
{  
  int x = event->pos().x();
  int y = event->pos().y();

  if (event->buttons() == Qt::NoButton)
    {
      m_prevMouseX = x;
      m_prevMouseY = y;
      
      OnScreenMenu::paintMaskMenu()->checkPaintArea(x, y);
      OnScreenMenu::maskMenu()->checkTag(x, y);
      OnScreenMenu::maskMenu()->checkThickness(x, y);
      OnScreenMenu::paintMaskMenu()->checkDThreshold(x, y);
      OnScreenMenu::paintMaskMenu()->checkDRadius(x, y);
      
      if (OnScreenMenu::paintMaskMenu()->menuActive() ||
	  OnScreenMenu::maskMenu()->menuActive())
	return true;

      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{

	  //      QPolygon tpoly = getPolygon(x, y);
	  //      
	  //      if (tpoly.count() > 0)
	  //	{
	  //	  generateImage();
	  //      
	  //	  QPainter polyPainter(&m_doodleImage);
	  //	  QPen pen(QColor(120, 0, 200, 200), 2);
	  //	  polyPainter.setPen(pen);
	  //	  polyPainter.drawPolygon(tpoly);
	  //
	  //	  return true;
	  //	}
	  //      else
	  //	return false;
	  
	  QRect subrect;
	  QImage subimage = getImage(x, y, subrect);
	  
	  if (! subimage.isNull())
	    {	      
	      generateImage();
	      
	      QPainter polyPainter(&m_doodleImage);	  
	      polyPainter.drawImage(subrect, subimage);
	      return true;
	    }
	  else
	    return false;
	}
    }
  else if (event->buttons() == Qt::LeftButton)    
    {
      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{
	  QRect subrect;
	  QImage subimage = getImage(x, y, subrect);

	  if (! subimage.isNull())
	    {	      
	      addImage(subrect, subimage);

	      generateImage();

	      return true;
	    }
	}

      if (! OnScreenMenu::paintMaskMenu()->paintArea())
	addPoint(event->pos());

      return true;
    }
  else if (event->buttons() == Qt::RightButton)    
    {
      if (OnScreenMenu::paintMaskMenu()->paintArea())
	{
	  QRect subrect;
	  QImage subimage = getImage(x, y, subrect);
	  
	  if (! subimage.isNull())
	    {	      
	      if (removeImage(subrect, subimage))
		{
		  generateImage();		  
		  return true;
		}
	    }
	}
    }

  return false;
}

QPolygon
DoodlePad::getPolygon(int scrx, int scry)
{
  QPolygon tpoly;

  float d = m_depthMap[(m_height-1-scry)*m_width+scrx];
  Vec point = Vec(scrx, scry, d);

  point = m_Viewer->camera()->unprojectedCoordinatesOf(point);
  
  Vec pz = Vec(scrx, scry, d+0.02);
  pz = m_Viewer->camera()->unprojectedCoordinatesOf(pz);
  pz -= point;
  pz.normalize();

  Vec px = pz.orthogonalVec();
  px.normalize();
  Vec py = px^pz;

  if (d >= 1.0)
    return tpoly;
	  	  
  
  float odepth = (d+m_depthShift)*m_depthScale;
  int ni = 360;
  int prevthk = 1;
  for(int i=0; i<ni+1; i++)
    {
      float t = i/(float)ni;
      
      Vec pt0 = cos(6.28*t)*px + sin(6.28*t)*py;
      
      bool done = false;
      int thk = prevthk;
      
      // first go outwards
      while (!done && thk <= OnScreenMenu::paintMaskMenu()->dradius())
	{
	  Vec pt = point + pt0*thk;
	  pt = m_Viewer->camera()->projectedCoordinatesOf(pt);
	  int x = pt.x;
	  int y = m_height-1-pt.y;
	  
	  float cd = (m_depthMap[y*m_width + x]+m_depthShift)*m_depthScale;
	  if (fabs(odepth - cd) > OnScreenMenu::paintMaskMenu()->depthThreshold())
	    done = true;
	  
	  thk ++;
	}
      done = false;
      
      // now search inwards
      while (!done && thk >= 1)
	{
	  Vec pt = point + pt0*thk;
	  pt = m_Viewer->camera()->projectedCoordinatesOf(pt);
	  int x = pt.x;
	  int y = m_height-1-pt.y;
	  
	  float cd = (m_depthMap[y*m_width + x]+m_depthShift)*m_depthScale;
	  if (fabs(odepth - cd) < OnScreenMenu::paintMaskMenu()->depthThreshold())
	    {
	      tpoly << QPoint(x, y);
	      done = true;
	    }
	  thk --;
	}
      
      prevthk = thk;
    }
  
  return tpoly;
}


QImage
DoodlePad::getImage(int scrx, int scry, QRect& subrect)
{
  QImage subimage;

  float d = m_depthMap[(m_height-1-scry)*m_width+scrx];
  if (d >= 1.0)
    return subimage;
  
  QPoint opt = QPoint(scrx, scry);

  QHash<int, QPoint> hpt;
  int optidx = opt.x()*5000 + opt.y();
  hpt[optidx] = opt;
  
  QStack<QPoint> stack;
  stack.push(opt);

  int drad2 = OnScreenMenu::paintMaskMenu()->dradius() *
              OnScreenMenu::paintMaskMenu()->dradius();

  float odepth = (d+m_depthShift)*m_depthScale;

  while (!stack.isEmpty())
    {
      QPoint pos = stack.pop();

      int imin = qMax(0, pos.x()-1);
      int jmin = qMax(0, pos.y()-1);
      int imax = qMin(m_width-1, pos.x()+1);
      int jmax = qMin(m_height-1, pos.y()+1);

      for(int j=jmin; j<=jmax; j++)
	for(int i=imin; i<=imax; i++)
	  {
	    QPoint pt(i,j);
	    int ml = ( (pt.x()-opt.x())*(pt.x()-opt.x()) +
		       (pt.y()-opt.y())*(pt.y()-opt.y()) );
	    int ptidx = pt.x()*5000 + pt.y();
	    if (ml < drad2 &&
		! hpt.contains(ptidx))
	      {
		float d = m_depthMap[(m_height-1-j)*m_width+i];
		d = (d+m_depthShift)*m_depthScale;
		if (fabs(odepth-d) < OnScreenMenu::paintMaskMenu()->depthThreshold())
		  {
		    hpt[ptidx] = pt;
		    stack.push(pt);
		  }
	      }
	  }
    }

  int xmin = m_width;
  int xmax = 0;
  int ymin = m_height;
  int ymax = 0;

  QList<QPoint> vpt;
  vpt = hpt.values();

  for(int i=0; i<vpt.count(); i++)
    {
      int x = vpt[i].x();
      int y = vpt[i].y();

      xmin = qMin(xmin, x);
      xmax = qMax(xmax, x);
      ymin = qMin(ymin, y);
      ymax = qMax(ymax, y);
    }

  int wd = xmax-xmin+1;
  int ht = ymax-ymin+1;

  subrect = QRect(xmin, m_height-1-ymax, wd, ht);

  subimage = QImage(wd, ht, QImage::Format_ARGB32);
  subimage.fill(0);

  for(int i=0; i<vpt.count(); i++)
    {
      int x = vpt[i].x() - xmin;
      int y = vpt[i].y() - ymin;

      subimage.setPixel(x, ht-1-y, qRgba(50, 150, 50, 150));
    }

  // differentiate the edge
  const uchar *bits = subimage.bits();
  for(int x=1; x<wd-1; x++)
    for(int y=1; y<ht-1; y++)
      {
	if (bits[4*(y*wd+x)] > 0)
	  {
	    if (bits[4*((y-1)*wd+(x-1))] == 0 ||
		bits[4*((y-1)*wd+x)] == 0 ||
		bits[4*((y-1)*wd+(x+1))] == 0 ||
		bits[4*(y*wd+(x-1))] == 0 ||
		bits[4*(y*wd+(x+1))] == 0 ||
		bits[4*((y+1)*wd+(x-1))] == 0 ||
		bits[4*((y+1)*wd+x)] == 0 ||
		bits[4*((y+1)*wd+(x+1))] == 0)
	      subimage.setPixel(x, y, qRgba(10, 50, 10, 150));
	  }	
      }
  
  return subimage;
}

void
DoodlePad::addImage(QRect rect, QImage image)
{
  if (m_selectImage.isNull())
    {
      m_selectImageRect = rect;
      m_selectImage = image;

      return;
    }


  int six = m_selectImageRect.x();
  int siy = m_selectImageRect.y();

  int rix = rect.x();
  int riy = rect.y();

  int xmin = qMin(six, rix);
  int ymin = qMin(siy, riy);
      
  int xmax = rix + image.width();
  int ymax = riy + image.height();
  xmax = qMax(xmax,
	      six+
	      m_selectImage.width());
  ymax = qMax(ymax,
	      siy+
	      m_selectImage.height());
  
  int wd = xmax-xmin+1;
  int ht = ymax-ymin+1;


  QImage newimage = QImage(wd, ht, QImage::Format_ARGB32);
  newimage.fill(0);


  int shiftx = six-xmin;
  int shifty = siy-ymin;
  const uchar *bits = m_selectImage.bits();
  for(int i=0; i<m_selectImage.width(); i++)
    for(int j=0; j<m_selectImage.height(); j++)
      {
	if (bits[4*(j*m_selectImage.width()+i)] > 0)
	  newimage.setPixel(shiftx+i,
			    shifty+j,
			    qRgba(50, 150, 50, 150));
      }

  shiftx = rix-xmin;
  shifty = riy-ymin;
  bits = image.bits();
  for(int i=0; i<image.width(); i++)
    for(int j=0; j<image.height(); j++)
      {
	if (bits[4*(j*image.width()+i)] > 0)
	  newimage.setPixel(shiftx+i,
			    shifty+j,
			    qRgba(50, 150, 50, 150));
      }


  // differentiate the edge
  bits = newimage.bits();
  for(int x=1; x<wd-1; x++)
    for(int y=1; y<ht-1; y++)
      {
	if (bits[4*(y*wd+x)] > 0)
	  {
	    if (bits[4*((y-1)*wd+(x-1))] == 0 ||
		bits[4*((y-1)*wd+x)] == 0 ||
		bits[4*((y-1)*wd+(x+1))] == 0 ||
		bits[4*(y*wd+(x-1))] == 0 ||
		bits[4*(y*wd+(x+1))] == 0 ||
		bits[4*((y+1)*wd+(x-1))] == 0 ||
		bits[4*((y+1)*wd+x)] == 0 ||
		bits[4*((y+1)*wd+(x+1))] == 0)
	      newimage.setPixel(x, y, qRgba(10, 50, 10, 150));
	  }	
      }
 
  m_selectImageRect = QRect(xmin, ymin, wd, ht);
  m_selectImage = newimage;
}

bool
DoodlePad::removeImage(QRect rect, QImage image)
{
  if (m_selectImage.isNull() ||
      image.isNull())
    return false;


  int rwd = image.width();
  int rht = image.height();
  int rix = rect.x();
  int riy = rect.y();


  int swd = m_selectImage.width();
  int sht = m_selectImage.height();
  int six = m_selectImageRect.x();
  int siy = m_selectImageRect.y();


  if (rix+rwd <= six ||
      riy+rht <= siy ||
      rix >= six+swd ||
      riy >= siy+sht)
    return false;


  int shiftx = rect.x() - m_selectImageRect.x();
  int shifty = rect.y() - m_selectImageRect.y();

  const uchar* rbits = image.bits();
  const uchar* sbits = m_selectImage.bits();

  for(int i=0; i<rwd; i++)
    for(int j=0; j<rht; j++)
      {
	int x = i+shiftx;
	int y = j+shifty;	
	if (x>=0 && x<swd &&
	    y>=0 && y<sht)
	  {
	    if (rbits[4*(j*rwd+i)] > 0 &&
		sbits[4*(y*swd+x)] > 0)	      
	      m_selectImage.setPixel(x, y, 0);
	  }
      }

  return true;
}
